/**
 *    Copyright (C) 2022-present MongoDB, Inc.
 *
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the Server Side Public License, version 1,
 *    as published by MongoDB, Inc.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    Server Side Public License for more details.
 *
 *    You should have received a copy of the Server Side Public License
 *    along with this program. If not, see
 *    <http://www.mongodb.com/licensing/server-side-public-license>.
 *
 *    As a special exception, the copyright holders give permission to link the
 *    code of portions of this program with the OpenSSL library under certain
 *    conditions as described in each individual source file and distribute
 *    linked combinations including the program with the OpenSSL library. You
 *    must comply with the Server Side Public License in all respects for
 *    all of the code used other than as permitted herein. If you modify file(s)
 *    with this exception, you may extend this exception to your version of the
 *    file(s), but you are not obligated to do so. If you do not wish to do so,
 *    delete this exception statement from your version. If you delete this
 *    exception statement from all source files in the program, then also delete
 *    it in the license file.
 */

#include <cstddef>
#include <string>
#include <utility>

#include <absl/container/node_hash_map.h>

#include "mongo/base/string_data.h"
#include "mongo/db/exec/sbe/abt/abt_unit_test_literals.h"
#include "mongo/db/exec/sbe/abt/abt_unit_test_utils.h"
#include "mongo/db/query/optimizer/comparison_op.h"
#include "mongo/db/query/optimizer/syntax/expr.h"
#include "mongo/db/query/optimizer/syntax/syntax.h"
#include "mongo/unittest/assert.h"
#include "mongo/unittest/framework.h"
#include "mongo/unittest/inline_auto_update.h"
#include "mongo/util/str.h"


namespace mongo::optimizer {
namespace {
using namespace unit_test_abt_literals;

TEST(TestInfra, AutoUpdateExplain) {
    ABT tree = make<BinaryOp>(Operations::Add,
                              Constant::int64(1),
                              make<Variable>("very very very very very very very very very very "
                                             "very very long variable name with \"quotes\""));

    /**
     * To exercise the auto-updating behavior:
     *   1. Induce a failure: change something in the expected output.
     *   2. Run the test binary with the flag "--autoUpdateAsserts".
     *   3. Observe afterwards that the test file is updated with the correct output.
     */
    ASSERT_EXPLAIN_V2_AUTO(  // NOLINT (test auto-update)
        "BinaryOp [Add]\n"
        "|   Variable [very very very very very very very very very very very very long variable "
        "name with \"quotes\"]\n"
        "Const [1]\n",
        tree);

    // Test for short constant. It should not be inlined. The nolint comment on the string constant
    // itself is auto-generated.
    ABT tree1 = make<Variable>("short name");
    ASSERT_EXPLAIN_V2_AUTO(  // NOLINT (test auto-update)
        "Variable [short name]\n",
        tree1);

    ASSERT_EXPLAIN_V2_AUTO(  // NOLINT
        "Variable [short name]\n",
        tree1);

    // Exercise auto-updating behavior for numbers.
    double number = 0.5;
    ASSERT_NUMBER_EQ_AUTO(  // NOLINT (test auto-update)
        0.5,                // NOLINT (test auto-update)
        number);

    // Exercise range auto-updating behavior. If the actual is outside the expected range, the range
    // is adjusted to +-25%.
    size_t plansExplored = 95;
    ASSERT_BETWEEN_AUTO(  // NOLINT (test auto-update)
        71,
        118,
        plansExplored);
}


TEST(TestInfra, ABTLiterals) {
    // Demonstrate shorthand tree initialization using the ABT string literal constructors.
    auto expr =
        _binary("And", _binary("Lt", "0"_cint32, "1"_cint32), _binary("Lt", "1"_cint32, "2"_cint32))
            ._n;

    ASSERT_EXPLAIN_V2_AUTO(
        "BinaryOp [And]\n"
        "|   BinaryOp [Lt]\n"
        "|   |   Const [2]\n"
        "|   Const [1]\n"
        "BinaryOp [Lt]\n"
        "|   Const [1]\n"
        "Const [0]\n",
        expr);
}

TEST(TestInfra, GenerateABTLiterals) {
    // The following code fragment is in the same time used to construct an ABT and to assert that
    // it is explained correctly.
#define SHORTHAND_EXAMPLE_ABT \
    _binary("And", _binary("Lt", "0"_cint32, "1"_cint32), _binary("Lt", "1"_cint32, "2"_cint32))._n

#define SHORTHAND_EXAMPLE_STR(x) #x
#define SHORTHAND_EXAMPLE_XSTR(x) SHORTHAND_EXAMPLE_STR(x)

    // Test compilation of generated shorthand initialization code. Test that explaining the abt
    // generated by the code fragment above results in a string containing the same content.
    const std::string exampleStr = SHORTHAND_EXAMPLE_XSTR(SHORTHAND_EXAMPLE_ABT);
    const ABT exampleNode = SHORTHAND_EXAMPLE_ABT;
    const std::string exampleExplain = str::stream()
        << ExplainInShorthand{}.explain(exampleNode) << "._n";

    ASSERT_EQ(exampleStr, exampleExplain);

#undef SHORTHAND_EXAMPLE_STR
#undef SHORTHAND_EXAMPLE_XSTR

#undef SHORTHAND_EXAMPLE_ABT
}
}  // namespace
}  // namespace mongo::optimizer
